﻿using System;
using System.Collections.Generic;



namespace DailyFx.AI
{
	public class SimplePloyment : IPloyment
    {
		/// <summary>
		///		the blackboard of knowledges taken advantage by this ployment .
		/// </summary>
		private IBlackboard _blackboard;
		
		/// <summary>
		///		the zone domain id of some aspect .
		/// </summary>
		private Guid _zone;

		/// <summary>
		///		Indicates if this ployment owns the zone domain exclusively.
		/// </summary>
		private bool _zoneOwner = false;
   
		/// <summary>
		///		the detailed plan routing .
		/// </summary>
		private IPlan plan = null;
   
		/// <summary>
		///		the mediator to nominate which experts gonna work .
		/// </summary>
		private IMediator _mediator = null;

		/// <summary>
		///		list of all experts for solving problem(s)
		/// </summary>
		private List<IExpert> _experts = null;
   
		/// <summary>
		///		the parent driver to run this ployment .
		/// </summary>
		private IDriver _driver = null;

		/// <summary>
		///		Indicates if the ployment is running .
		/// </summary>
		private bool _isRunning = false;
		
		/// <summary>
		///		
		/// </summary>
		private IList<IPloymentListener> _listeners = new List<IPloymentListener> ();



		/// <summary>
		///		c'tor 
		/// </summary>
		public SimplePloyment ()
		{

		}

		/// <summary>
		///		Gets or sets the blackboard of knowledges taken advantage by this ployment .
		/// </summary>
		public IBlackboard Blackboard 
		{
			get {
				return _blackboard;
			}
			set{
				if ( value == null )
					throw new ArgumentException( "Cannot bind null blackboard.");

				_blackboard = value;
			}
		}

		/// <summary>
		///		Gets or sets the zone domain of this ployment .
		/// </summary>
		public Guid Zone
		{
			get {
				return this._zone;
			}
			set {
				this._zone = value;
			}
		}

		public bool ZoneOwner 
		{
			get {
				return this._zoneOwner;
			}
			set {
				this._zoneOwner = value;
			}
		}

		/// <summary>
		///		Gets or sets the concrete plan to work .
		/// </summary>
		public IPlan Plan 
		{
			get {
				return this.plan;
			}
			set {
				this.plan = value;
			}
		}

		/// <summary>
		///		Gets or sets the mediator to nominate exports .
		/// </summary>
		public IMediator Mediator 
		{
			get {
				return this._mediator;
			}
			set {
				this._mediator = value;
			}
		}

		/// <summary>
		///		Gets the experts' list on the ployment .
		/// </summary>
		public IReadOnlyList<IExpert> Experts
		{
			get {
				return this._experts.AsReadOnly() ;
			}
		}

		/// <summary>
		///		Gets or sets the driver to run this ployment.
		/// </summary>
		public IDriver Driver 
		{
			get {
				return this._driver;
			}
			set {
				if ( this._driver != null )
					this._driver.disposePloyment ( this);

				this._driver = null;
				this._driver = value;
				this._driver.applyPloyment ( this );
			}
		}


		public bool Run ()
		{
			if ( this._isRunning )
				throw new InvalidOperationException( "reentrant run loop detected!");
			
			// Markup this ployment is now running .
			this._isRunning = true;
			
			bool toRun = false;
			try 
			{
				this.employApplicableExperts ( this._blackboard );

				IReadOnlyList<ITask> tasks = this.plan.estimatePossiblyTasks( this._blackboard );
				if ( tasks == null || tasks.Count == 0 )
					throw new InvalidOperationException ("plan did not rosolve any task.");

				this.notifyTasksDecided (tasks);

				IExpert expert = this.nominateExpert( tasks, this._blackboard);
				if (expert != null) 
				{
					toRun = true;
					this.notifyExpertNominated ( expert);
					expert.Run ( this._blackboard);
				}

				// terminate this ployment ?
				foreach ( ITask task in tasks )
				{
					if ( task.final() )
					{
						toRun = false;
						this.Stop ();
						
						break;
					}
				}
			}
			finally
			{
				this._isRunning = false;
			}

			return toRun ;
		}


		public void Stop ()
		{
			this._driver.disposePloyment( this);
			this._driver = null;

			if (this._zoneOwner ) {
				this._blackboard.closeZone( _zone);
			}
		}


		private IExpert nominateExpert ( IReadOnlyList<ITask> tasks, IBlackboard blackboard) 
		{
			foreach ( var task in tasks) 
			{
				IReadOnlyCollection<IExpert> experts = this._mediator.mediate( task);

				if ( experts == null) 
					continue;

				foreach ( var exp in experts )
				{
					if (exp.awared (blackboard) )
					{
						return exp;
					}
				}
			}

			return null;
		}


		private void employApplicableExperts ( IBlackboard blackboard ) 
		{
			if ( this._experts == null || this._experts.Count == 0)
			{
				return;
			}

			foreach ( var exp in this._experts )
			{
				if (exp.awared( blackboard) )
				{
					exp.Run(blackboard);
				}
			}
		}




		/// <summary>
		/// 
		/// </summary>
		public void registerListener ( IPloymentListener listener)
		{
			if ( listener == null )
				return;

			if ( !this._listeners.Contains(listener)) 
				this._listeners.Add(listener);
		}
		/// <summary>
		/// 
		/// </summary>
		public void removeListener ( IPloymentListener listener)
		{
			this._listeners.Remove( listener );
		}


		/// <summary>
		/// 
		/// </summary>
		private void notifyTasksDecided (IReadOnlyList<ITask> tasks ) 
		{
			foreach ( var listener in this._listeners)
				listener.OnTasksDecided ( this, tasks );
		}
		/// <summary>
		/// 
		/// </summary>
		private void notifyExpertNominated ( IExpert expert )
		{
			foreach ( var listener in this._listeners)
				listener.OnExpertNominated (this, expert);
		}

	
    }

}
